package com.gitee.wsl.struct.command.model.parameters.options.bean

import com.gitee.wsl.struct.command.context.Context
import com.gitee.wsl.struct.command.model.parameters.options.Option
import com.gitee.wsl.struct.command.model.parameters.options.delegate.OptionWithValues
import com.gitee.wsl.struct.command.model.parameters.options.inferEnvvar
import com.gitee.wsl.struct.command.parsers.OptionInvocation
import com.gitee.wsl.struct.command.context.sources.ValueSource

internal sealed class FinalValue {
    data class Parsed(val values: List<OptionInvocation>) : FinalValue()
    data class Sourced(val values: List<ValueSource.Invocation>) : FinalValue()
    data class Envvar(val key: String, val value: String) : FinalValue()
}

internal fun Option.getFinalValue(
    context: Context,
    invocations: List<OptionInvocation>,
    envvar: String?,
): FinalValue {
    return when {
        // We don't look at envvars or the value source for eager options
        eager || invocations.isNotEmpty() -> FinalValue.Parsed(invocations)

        context.readEnvvarBeforeValueSource -> {
            readEnvVar(context, envvar) ?: readValueSource(context)
        }

        else -> {
            readValueSource(context) ?: readEnvVar(context, envvar)
        }
    } ?: FinalValue.Parsed(emptyList())
}

// This is a pretty ugly hack: option groups need to enforce their constraints, including on options
// from envvars/value sources, but not including default values. Unfortunately, we don't know
// whether an option's value is from a default or envvar. So we do some ugly casts and read the
// final value again to check for values from other sources.
internal fun Option.hasEnvvarOrSourcedValue(
    context: Context,
    invocations: List<OptionInvocation>,
): Boolean {
    val envvar = (this as? OptionWithValues<*, *, *>)?.envvar
    val final = this.getFinalValue(context, invocations, envvar)
    return final !is FinalValue.Parsed
}

private fun Option.readValueSource(context: Context): FinalValue? {
    return context.valueSource?.getValues(context, this)?.ifEmpty { null }
        ?.let { FinalValue.Sourced(it) }
}

private fun Option.readEnvVar(context: Context, envvar: String?): FinalValue? {
    val env = inferEnvvar(names, envvar, context.autoEnvvarPrefix) ?: return null
    return context.readEnvvar(env)?.let { FinalValue.Envvar(env, it) }
}